﻿using System.Collections.Immutable;
using System.Text;
using DimensionsHelper.LanguageService.Protocol;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Serialization;

public ref struct SyntaxWriter
{
    private readonly bool _expandInclude;
    private readonly int _indentSpace;

    private int _indentLevel;

    // 展开 #include 文件后，后续的节点需要在开头标记行号
    private bool _needLineDirective;

    // 上一个文本区间
    private ITextRange? _last;

    // 是否保持节点前原有的空格
    private bool _keepLeadingSpace;

    private SyntaxWriter(int indentSpace, bool expandInclude = false, bool keepLeadingSpace = false)
    {
        _expandInclude = expandInclude;
        _indentSpace = indentSpace;
        _indentLevel = 0;
        _keepLeadingSpace = keepLeadingSpace;
    }


    private readonly void WriteIndent(StreamWriter writer)
    {
        if (_indentLevel <= 0 || _indentSpace <= 0)
        {
            return;
        }

        writer.Write(new string(' ', _indentLevel * _indentSpace));
    }


    private readonly void StartLine(StreamWriter writer)
    {
        if (writer.BaseStream.Position == 0)
        {
            return;
        }

        writer.WriteLine();
        WriteIndent(writer);
    }


    private readonly void EndBlock(StreamWriter writer, string endsText = "")
    {
        if (!string.IsNullOrEmpty(endsText))
        {
            StartLine(writer);
            writer.Write(endsText);
        }
    }


    private static void WriteToken(SyntaxKinds kind, StreamWriter writer)
    {
        writer.Write(SyntaxFacts.ConvertTokenToString(kind));
    }


    private readonly void SkipLinesAhead(StreamWriter writer, ITextRange range)
    {
        if (_last == null)
        {
            int startLine = range.StartLine;
            while (--startLine > 0)
            {
                writer.WriteLine();
            }

            return;
        }

        int lines = range.StartLine - _last.EndLine;
        while (--lines > 0)
        {
            writer.WriteLine();
        }
    }


    private void WriteComment(StreamWriter writer, SyntaxComment trivia)
    {
        StartLine(writer);

        if (trivia.CommentKind == SyntaxCommentKinds.BlockComment)
        {
            writer.Write("'!");
            _indentLevel++;
            StartLine(writer);
            foreach (SyntaxCommentLine line in trivia.GetLines())
            {
                writer.Write(line);
                StartLine(writer);
            }

            _indentLevel--;
            writer.Write("!'");
        }
        else
        {
            foreach (SyntaxCommentLine line in trivia.GetLines())
            {
                writer.Write('\'');
                writer.Write(line);
            }
        }
    }


    private void WriteExpression(StreamWriter writer, Expression expression)
    {
        if (expression.LeftParenthesisToken != null)
        {
            writer.Write('(');
        }

        switch (expression.Kind)
        {
            case SyntaxKinds.Integer:
                WriteIntegerLiteral(writer, (IntegerLiteral)expression);
                break;

            case SyntaxKinds.Double:
                WriteDoubleLiteral(writer, (DoubleLiteral)expression);
                break;

            case SyntaxKinds.String:
                WriteStringLiteral(writer, (StringLiteral)expression);
                break;

            case SyntaxKinds.Boolean:
                WriteBooleanLiteral(writer, (BooleanLiteral)expression);
                break;

            case SyntaxKinds.Categorical:
                WriteCategoricalLiteral(writer, (CategoricalLiteral)expression);
                break;

            case SyntaxKinds.Identifier:
                WriteIdentifier(writer, (Identifier)expression);
                break;

            case SyntaxKinds.MemberExpression:
                WriteMemberExpression(writer, (MemberExpression)expression);
                break;

            case SyntaxKinds.IndexExpression:
                WriteIndexExpression(writer, (IndexExpression)expression);
                break;

            case SyntaxKinds.CallExpression:
                WriteCallExpression(writer, (CallExpression)expression);
                break;

            case SyntaxKinds.UnaryExpression:
                WriteUnaryExpression(writer, (UnaryExpression)expression);
                break;

            case SyntaxKinds.BinaryExpression:
                WriteBinaryExpression(writer, (BinaryExpression)expression);
                break;

            case SyntaxKinds.AssignmentExpression:
                WriteAssignmentExpression(writer, (AssignmentExpression)expression);
                break;
        }

        if (expression.RightParenthesisToken != null)
        {
            writer.Write(')');
        }
    }


    private static void WriteIntegerLiteral(StreamWriter writer, IntegerLiteral integerLiteral)
    {
        writer.Write(integerLiteral.Value);
    }


    private static void WriteDoubleLiteral(StreamWriter writer, DoubleLiteral doubleLiteral)
    {
        writer.Write(doubleLiteral.Value);
    }


    private static void WriteStringLiteral(StreamWriter writer, StringLiteral stringLiteral)
    {
        writer.Write('"');
        writer.Write(stringLiteral.Value);
        writer.Write('"');
    }


    private static void WriteBooleanLiteral(StreamWriter writer, BooleanLiteral boolLiteral)
    {
        writer.Write(boolLiteral.Value);
    }


    private static void WriteCategoryLiteral(StreamWriter writer, CategoryLiteral categoryLiteral)
    {
        if (categoryLiteral.CaretToken != null)
        {
            writer.Write('^');
        }

        if (categoryLiteral.IsIntegerValue)
        {
            writer.Write(categoryLiteral.IntegerValue);
        }
        else if (categoryLiteral.IsCategoryName)
        {
            WriteIdentifier(writer, categoryLiteral.RangeLeft);
        }
        else if (categoryLiteral.IsCategoryRange)
        {
            WriteIdentifier(writer, categoryLiteral.RangeLeft);
            writer.Write("..");
            WriteIdentifier(writer, categoryLiteral.RangeRight);
        }
    }


    private static void WriteCategoricalLiteral(StreamWriter writer, CategoricalLiteral categoricalLiteral)
    {
        writer.Write('{');

        for (int i = 0; i < categoricalLiteral.Count; i++)
        {
            if (i > 0)
            {
                writer.Write(", ");
            }

            WriteCategoryLiteral(writer, categoricalLiteral[i]);
        }

        writer.Write('}');
    }


    private static void WriteIdentifier(StreamWriter writer, ITextNode? identifier)
    {
        if (identifier != null)
        {
            writer.Write(identifier.Text);
        }
    }


    private void WriteMemberExpression(StreamWriter writer, MemberExpression memberExpression)
    {
        WriteExpression(writer, memberExpression.Object);
        writer.Write('.');
        WriteIdentifier(writer, memberExpression.Member);
    }


    private void WriteArgumentList(StreamWriter writer, ArgumentList? argumentList)
    {
        if (argumentList != null)
        {
            for (int i = 0; i < argumentList.Count; i++)
            {
                if (i > 0)
                {
                    writer.Write(", ");
                }

                WriteExpression(writer, argumentList[i]);
            }
        }
    }


    private void WriteIndexExpression(StreamWriter writer, IndexExpression indexExpression)
    {
        WriteExpression(writer, indexExpression.Object);
        writer.Write('[');
        WriteArgumentList(writer, indexExpression.Arguments);
        writer.Write(']');
    }


    private void WriteCallExpression(StreamWriter writer, CallExpression callExpression)
    {
        WriteExpression(writer, callExpression.Callee);
        writer.Write('(');
        WriteArgumentList(writer, callExpression.Arguments);
        writer.Write(')');
    }


    private void WriteUnaryExpression(StreamWriter writer, UnaryExpression unaryExpression)
    {
        writer.Write("Not ");
        WriteExpression(writer, unaryExpression.Expression);
    }


    private void WriteBinaryExpression(StreamWriter writer, BinaryExpression binaryExpression)
    {
        WriteExpression(writer, binaryExpression.LeftOperand);
        writer.Write(' ');
        WriteToken(binaryExpression.OperatorToken.Kind, writer);
        writer.Write(' ');
        WriteExpression(writer, binaryExpression.RightOperand);
    }


    private void WriteAssignmentExpression(StreamWriter writer, AssignmentExpression assignmentExpression)
    {
        if (assignmentExpression.SetKeywordToken != null)
        {
            writer.Write("Set ");
        }

        WriteExpression(writer, assignmentExpression.LeftOperand);
        writer.Write(" = ");
        WriteExpression(writer, assignmentExpression.RightOperand);
    }


    private static void WriteVariableDeclaration(StreamWriter writer, VariableDeclaration variableDeclaration)
    {
        WriteIdentifier(writer, variableDeclaration.NameIdentifier);
    }


    private static void WriteArrayDeclaration(StreamWriter writer, ArrayDeclaration arrayDeclaration)
    {
        WriteVariableDeclaration(writer, arrayDeclaration);

        for (int i = 0; i < arrayDeclaration.Rank; i++)
        {
            writer.Write('[');
            IntegerLiteral? bound = arrayDeclaration.GetBoundary(i + 1);
            if (bound != null)
            {
                WriteIntegerLiteral(writer, bound);
            }

            writer.Write(']');
        }
    }


    private static void WriteDimStatement(StreamWriter writer, DimStatement dimStatement)
    {
        writer.Write("Dim ");
        IReadOnlyList<VariableDeclaration> decls = dimStatement.Declarations;

        for (int i = 0; i < decls.Count; i++)
        {
            if (i > 0)
            {
                writer.Write(", ");
            }

            VariableDeclaration decl = decls[i];

            if (decl.IsNode(SyntaxKinds.VariableDeclaration))
            {
                WriteVariableDeclaration(writer, decl);
            }
            else
            {
                WriteArrayDeclaration(writer, (ArrayDeclaration)decl);
            }
        }
    }


    private void WriteStatement(StreamWriter writer, Statement statement)
    {
        StartLine(writer);

        switch (statement.Kind)
        {
            case SyntaxKinds.ExpressionStatement:
                WriteExpressionStatement(writer, (ExpressionStatement)statement);
                break;

            case SyntaxKinds.DimStatement:
                WriteDimStatement(writer, (DimStatement)statement);
                break;

            case SyntaxKinds.IfStatement:
                WriteIfStatement(writer, (IfStatement)statement);
                break;

            case SyntaxKinds.ForStatement:
                WriteForStatement(writer, (ForStatement)statement);
                break;

            case SyntaxKinds.ForEachStatement:
                WriteForEachStatement(writer, (ForEachStatement)statement);
                break;

            case SyntaxKinds.WhileStatement:
                WriteWhileStatement(writer, (WhileStatement)statement);
                break;

            case SyntaxKinds.DoStatement:
                WriteDoStatement(writer, (DoStatement)statement);
                break;

            case SyntaxKinds.SelectStatement:
                WriteSelectStatement(writer, (SelectStatement)statement);
                break;

            case SyntaxKinds.WithStatement:
                WriteWithStatement(writer, (WithStatement)statement);
                break;

            case SyntaxKinds.OnErrorStatement:
                WriteOnErrorStatement(writer, (OnErrorStatement)statement);
                break;

            case SyntaxKinds.ExitStatement:
                WriteExitStatement(writer, (ExitStatement)statement, true);
                break;
        }
    }


    private void WriteBlock(StreamWriter writer, Block block, bool newLine)
    {
        if (newLine)
        {
            StartLine(writer);
        }

        foreach (Statement stat in block.Statements)
        {
            WriteStatement(writer, stat);
        }
    }


    private void WriteExpressionStatement(StreamWriter writer, ExpressionStatement statement)
    {
        WriteExpression(writer, statement.Expression);
    }


    private void WriteIfStatement(StreamWriter writer, IfStatement ifStatement)
    {
        if (ifStatement.HasCondition)
        {
            writer.Write(ifStatement.PreviousBranch == null ? "If " : "ElseIf ");
            WriteExpression(writer, ifStatement.ConditionExpression);
            writer.Write(" Then");
        }
        else
        {
            writer.Write("Else");
        }

        if (ifStatement.IsSingleLineStatement)
        {
            writer.Write(' ');
            WriteBlock(writer, ifStatement.Body, false);

            if (ifStatement.ElseBranch != null)
            {
                writer.Write(" Else ");
                WriteBlock(writer, ifStatement.ElseBranch.Body, false);
            }
        }
        else
        {
            _indentLevel++;
            WriteBlock(writer, ifStatement.Body, true);
            _indentLevel--;

            if (ifStatement.ElseBranch != null)
            {
                WriteIfStatement(writer, ifStatement.ElseBranch);
            }
            else
            {
                EndBlock(writer, "End If");
            }
        }
    }


    private void WriteRangeExpression(StreamWriter writer, RangeExpression range)
    {
        WriteExpression(writer, range.LeftBoundary);
        writer.Write(" To ");
        WriteExpression(writer, range.RightBoundary);
    }


    private void WriteForStatement(StreamWriter writer, ForStatement forStatement)
    {
        writer.Write("For ");
        WriteIdentifier(writer, forStatement.CounterIdentifier);
        writer.Write(" = ");
        WriteRangeExpression(writer, forStatement.RangeExpression);

        if (forStatement.StepInteger != null && !Expression.IsEmptyExpression(forStatement.StepInteger))
        {
            writer.WriteLine(" Step ");
            WriteIntegerLiteral(writer, forStatement.StepInteger);
        }

        _indentLevel++;
        WriteBlock(writer, forStatement.Body, true);
        _indentLevel--;

        EndBlock(writer, "Next");
    }


    private void WriteForEachStatement(StreamWriter writer, ForEachStatement forEachStatement)
    {
        writer.Write("For Each ");
        WriteIdentifier(writer, forEachStatement.ElementIdentifier);
        writer.Write(" In ");
        WriteExpression(writer, forEachStatement.GroupExpression);
        _indentLevel++;
        WriteBlock(writer, forEachStatement.Body, true);
        _indentLevel--;
        EndBlock(writer, "Next");
    }


    private void WriteWhileStatement(StreamWriter writer, WhileStatement whileStatement)
    {
        writer.Write("While ");
        WriteExpression(writer, whileStatement.ConditionExpression);
        _indentLevel++;
        WriteBlock(writer, whileStatement.Body, true);
        _indentLevel--;
        EndBlock(writer, "End While");
    }


    private void WriteDoStatement(StreamWriter writer, DoStatement doStatement)
    {
        writer.Write("Do");
        _indentLevel++;
        WriteBlock(writer, doStatement.Body, true);
        _indentLevel--;
        EndBlock(writer, "Loop ");
        writer.Write(doStatement.WhileOrUntilKeywordToken.IsNode(SyntaxKinds.WhileStatement) ? "While " : "Until ");
        WriteExpression(writer, doStatement.ConditionExpression);
    }


    private void WriteSelectCaseComparision(StreamWriter writer, SelectCaseComparison selectCaseComparison)
    {
        if (selectCaseComparison.OperatorToken != null)
        {
            WriteToken(selectCaseComparison.OperatorToken.Kind, writer);
            writer.Write(' ');
        }

        WriteExpression(writer, selectCaseComparison.Expression);
    }


    private void WriteSelectCaseClause(StreamWriter writer, SelectCaseClause selectCaseClause)
    {
        writer.Write("Case ");
        if (selectCaseClause.ElseKeywordToken != null)
        {
            writer.Write("Else");
        }
        else
        {
            ImmutableArray<SelectCaseComparison> conditions = selectCaseClause.Conditions;

            for (int i = 0; i < conditions.Length; i++)
            {
                if (i > 0)
                {
                    writer.Write(", ");
                }

                WriteSelectCaseComparision(writer, conditions[i]);
            }
        }

        _indentLevel++;
        WriteBlock(writer, selectCaseClause.Body, true);
        _indentLevel--;
    }


    private void WriteSelectStatement(StreamWriter writer, SelectStatement selectStatement)
    {
        writer.Write("Select Case ");
        WriteExpression(writer, selectStatement.Expression);

        _indentLevel++;

        foreach (SelectCaseClause clause in selectStatement)
        {
            WriteSelectCaseClause(writer, clause);
        }

        _indentLevel--;

        EndBlock(writer, "End Select");
    }


    private void WriteWithStatement(StreamWriter writer, WithStatement withStatement)
    {
        writer.Write("With ");
        WriteExpression(writer, withStatement.Expression);

        _indentLevel++;
        WriteBlock(writer, withStatement.Body, false);
        _indentLevel--;

        EndBlock(writer, "End With");
    }


    private readonly void WriteGotoStatement(StreamWriter writer, GotoStatement gotoStatement, bool newLine)
    {
        if (newLine)
        {
            StartLine(writer);
        }

        writer.Write("GoTo ");

        if (gotoStatement.ZeroLiteral != null)
        {
            writer.Write('0');
        }
        else if (gotoStatement.LabelIdentifier != null)
        {
            WriteIdentifier(writer, gotoStatement.LabelIdentifier);
        }
    }


    private readonly void WriteExitStatement(StreamWriter writer, ExitStatement exitStatement, bool newLine)
    {
        if (newLine)
        {
            StartLine(writer);
        }

        writer.Write("Exit");

        if (exitStatement.ProcedureKeywordToken != null)
        {
            writer.Write(' ');
            WriteToken(exitStatement.ProcedureKeywordToken.Kind, writer);
        }
    }


    private readonly void WriteOnErrorStatement(StreamWriter writer, OnErrorStatement onErrorStatement)
    {
        writer.Write("On Error ");

        if (onErrorStatement.JumpStatement != null)
        {
            WriteGotoStatement(writer, onErrorStatement.JumpStatement, false);
        }
        else if (onErrorStatement.ResumeStatement != null)
        {
            writer.Write("Resume Next");
        }
    }


    private static void WriteLineDirective(StreamWriter writer, int line, string filePath)
    {
        writer.WriteLine($"#line {line} {filePath}");
    }


    private void CheckAndWriteLineDirective(StreamWriter writer, ITextRange stat)
    {
        if (!_needLineDirective)
        {
            return;
        }

        WriteLineDirective(writer, stat.StartLine, DocumentUri.UriToFsPath(stat.Uri, true));
        _needLineDirective = false;
    }


    private void WriteIncludeDirective(StreamWriter writer, FileInclude fileInclude)
    {
        if (_expandInclude)
        {
            // 只写入存在的文件
            if (fileInclude.Source != null)
            {
                WriteLineDirective(writer, 1, fileInclude.Source.FileName);
            }

            _needLineDirective = true;
            return;
        }

        writer.Write("#include ");
        if (fileInclude.IncludeFileName.IsNode(SyntaxKinds.String))
        {
            writer.Write('"');
            writer.Write(fileInclude.IncludeFileName.Text);
            writer.Write('"');
        }
        else
        {
            writer.Write(fileInclude.IncludeFileName.Text);
        }
    }


    private void WriteDeclaration(StreamWriter writer, IDeclaration declaration)
    {
        StartLine(writer);

        switch (declaration.Kind)
        {
            case SyntaxKinds.MacroDeclaration:
                WriteMacroDeclaration(writer, (Macro)declaration);
                break;

            case SyntaxKinds.FunctionDeclaration:
                WriteFunctionDeclaration(writer, (FunctionDeclaration)declaration);
                break;
        }
    }


    private void WriteMacroDeclaration(StreamWriter writer, Macro macro)
    {
        writer.Write("#define ");
        WriteIdentifier(writer, macro.NameIdentifier);

        if (macro.ParameterList?.Count > 0)
        {
            writer.Write('(');

            for (int i = 0; i < macro.ParameterList.Count; i++)
            {
                if (i > 0)
                {
                    writer.Write(", ");
                }

                WriteIdentifier(writer, macro.ParameterList[i].NameNode);
            }
        }

        if (macro.DefinitionCount > 0)
        {
            foreach (SourceNode def in macro.Definitions)
            {
                writer.Write(' ');
                WriteNode(writer, def);
            }
        }
    }


    private void WriteFunctionDeclaration(StreamWriter writer, FunctionDeclaration functionDeclaration)
    {
        WriteToken(functionDeclaration.DeclareKeywordToken.Kind, writer);
        writer.Write(' ');
        WriteIdentifier(writer, functionDeclaration.NameIdentifier);
        writer.Write('(');

        for (int i = 0; i < functionDeclaration.ParametersCount; i++)
        {
            if (i > 0)
            {
                writer.Write(", ");
            }

            WriteDeclaration(writer, functionDeclaration.Parameters[i]);
        }

        writer.Write(')');

        _indentLevel++;
        WriteBlock(writer, functionDeclaration.Body, true);
        _indentLevel--;

        StartLine(writer);
        writer.Write("End ");
        WriteToken(functionDeclaration.DeclareKeywordToken.Kind, writer);
    }


    private void WriteNode(StreamWriter writer, ITextNode span)
    {
        CheckAndWriteLineDirective(writer, span);

        foreach (SyntaxComment trivia in span.GetLeadingComments())
        {
            SkipLinesAhead(writer, trivia);
            WriteComment(writer, trivia);
            _last = trivia;
        }

        SkipLinesAhead(writer, span);

        switch (span)
        {
            case SyntaxToken token:
                WriteToken(token.Kind, writer);
                break;

            case Expression expression:
                WriteExpression(writer, expression);
                break;

            case Declaration declaration:
                WriteDeclaration(writer, declaration);
                break;

            case Statement statement:
                WriteStatement(writer, statement);
                break;

            case FileInclude include:
                WriteIncludeDirective(writer, include);
                break;
        }

        _last = span;
    }

    public static void Write(Stream stream, IEnumerable<ITextNode> nodes, Encoding encoding, int indentSpace = 4,
        bool expandInclude = false, bool keepLeadingSpace = false)
    {
        using StreamWriter streamWriter = new(stream, encoding);
        streamWriter.AutoFlush = true;
        SyntaxWriter writer = new(indentSpace, expandInclude, keepLeadingSpace);
        foreach (ITextNode node in nodes)
        {
            writer.WriteNode(streamWriter, node);
        }

        streamWriter.Close();
    }


    public static void Write(Stream stream, SyntaxTree tree, Encoding encoding, int indentSpace = 4,
        bool expandInclude = false, bool keepLeadingSpace = false)
    {
        Write(stream, tree.GetNodes(), encoding, indentSpace, expandInclude, keepLeadingSpace);
    }
}